package main

import (
	"encoding/gob"
	"encoding/json"
	"fmt"
	"github.com/alyu/configparser"
	"github.com/go-martini/martini"
	"github.com/martini-contrib/render"
	"github.com/martini-contrib/sessions"
	"html/template"
	"io/ioutil"
	"log"
	"os"
	"os/signal"
	"strings"
	"syscall"
	"time"
)

var userMap map[string]UserInfo
var jobMap map[string]JobInfo
var debug bool = true
var addr, users, jobs, logs string
var configFile string
var logFile *os.File

func main() {
	gob.Register(UserInfo{})
	configFile = "./config.ini"
	if debug {
		fmt.Println(os.Args)
	}
	if len(os.Args) >= 2 {
		configFile = os.Args[1]
	}
	config, err := configparser.Read(configFile)
	if err != nil {
		fmt.Println("wrong config file path!!!", err)
	} else {
		sec, ers := config.Section("service")
		if ers != nil {
			fmt.Println("wrong config file format:", ers)
		} else {
			addr = sec.ValueOf("addr")
			users = sec.ValueOf("user")
			jobs = sec.ValueOf("jobs")
			logs = sec.ValueOf("logs")
			debug = sec.ValueOf("mode") == "1"
			lf, er := os.OpenFile(logs, os.O_RDWR|os.O_CREATE|os.O_SYNC, os.ModePerm)
			if er != nil {
				fmt.Println("can't start ServiceManager when open log file:", er)
				return
			}
			logFile = lf
			userMap = getUserMap(users)
			jobMap, _ = getJobMap(jobs)
			if len(jobMap) > 0 {
				go startJobs()
			}
			m := martini.Classic()
			store := sessions.NewCookieStore([]byte("secret123"))
			m.Logger(log.New(logFile, "[ServiceManager]", 0))
			m.Use(martini.Static("statics", martini.StaticOptions{Prefix: "statics", SkipLogging: true}))
			m.Use(martini.Static("icon", martini.StaticOptions{Prefix: "", SkipLogging: true}))
			m.Use(sessions.Sessions("sessions", store))
			m.Use(render.Renderer(render.Options{
				Directory:  "templates",                // Specify what path to load the templates from.
				Layout:     "layout",                   // Specify a layout template. Layouts can call {{ yield }} to render the current template.
				Extensions: []string{".tmpl", ".html"}, // Specify extensions to load for templates.
				Charset:    "UTF-8",                    // Sets encoding for json and html content-types. Default is "UTF-8".
				IndentJSON: true,                       // Output human readable JSON
				Funcs: []template.FuncMap{{
					"typeOfStatus": typeOfStatus,
					"regexp":       regexpMach,
					"permlevel":    getPermissionCode,
					"permtrans":    translatePermission,
					"jsonstr":      jsonstr}},
			}))
			m.Get("/", Excutor{home}.render)
			m.Get("/index", Excutor{home}.render)
			m.Get("/userList", Excutor{userList}.render)
			m.Get("/login", Excutor{login}.render)
			m.Post("/login", Excutor{login}.render)
			m.Get("/logout", Excutor{logout}.render)
			m.Post("/stop/:name", Excutor{stopJobInfo}.render)
			m.Post("/start/:name", Excutor{startJobInfo}.render)
			m.Post("/build/:name", Excutor{buildJobInfo}.render)
			m.Get("/userInfo/:name", Excutor{userInfo}.render)
			m.Post("/userInfo", Excutor{userInfo}.render)           //编辑用户
			m.Post("/deleteUser/:name", Excutor{deleteUser}.render) //删除用户
			m.Get("/config", Excutor{configInfo}.render)
			m.Get("/jobInfo/:name", Excutor{jobInfo}.render)
			m.Post("/jobInfo", Excutor{jobInfo}.render)
			m.Post("/deleteJob/:name", Excutor{deleteJob}.render)  //删除用户
			m.Get("/logInfo/:name/:type", Excutor{logInfo}.render) //删除用户
			m.Get("/logInfo", Excutor{logInfo}.render)             //删除用户
			m.Post("/logInfo", Excutor{logInfo}.render)
			m.Post("/storeLog", Excutor{storeLog}.render)
			m.Post("/checkJobName", Excutor{checkJobName}.render)
			m.Post("/hook/:name", Excutor{startHookJob}.render)
			m.Get("/kill", Excutor{killSelf}.render)
			go renewLog()
			go m.RunOnAddr(addr)
			c := make(chan os.Signal, 1)
			signal.Notify(c, syscall.SIGINT, syscall.SIGTERM, syscall.SIGKILL)
			s := <-c
			fmt.Println(s)
			for _, job := range jobMap {
				if job.RunStatus == 1 {
					job.RunProcess.Kill()
					fmt.Println("kill running process of", job.Name)
				}
				if job.HookStatus == 1 {
					job.HookProcess.Kill()
				}
				if debug {
					fmt.Println("kill job ", job.Name)
				}
			}
			fmt.Println("i had been killed")
			os.Exit(0)

		}
	}
}
func listenKillself(finished chan struct{}) {

	close(finished)
}
func killSelf(model RequestModel) {
	if model.session.Get("user") != nil {
		user := model.session.Get("user").(UserInfo)
		if regexpMach(user.Password, "ADMIN") {
			model.r.Text(200, "hello")
			os.Exit(0)
		}

	}

}
func jsonstr(dd interface{}) string {
	bb, er := json.MarshalIndent(dd, "    ", "")

	if er != nil {
		return er.Error()
	}
	return string(bb)
}
func startJobs() {
	for _, j := range jobMap {
		go startJob(j)
	}
}
func startJobByName(jobName string) {
	job, ok := jobMap[jobName]
	if ok {
		startJob(job)
	}
}
func stopJobByName(name string) {
	job, ok := jobMap[name]
	if ok {
		stopJob(job)
	}
}
func stopJob(job JobInfo) {
	if job.RunProcess != nil {
		job.RunStatus = 2

		job.RunLogFile.Write([]byte(fmt.Sprint(time.Now(), " |stopping job '"+job.Name+"' at ")))
		error := job.RunProcess.Kill()
		if error != nil {
			fmt.Print(error)
			job.pushError(error)
			job.RunStatus = -1
		} else {
			job.RunStatus = 2
		}
		if debug {
			fmt.Println(jobMap[job.Name])
			fmt.Println(job)
		}
		jobMap[job.Name] = job
		job.RunLogFile.Write([]byte(fmt.Sprintln(time.Now(), " |job '"+job.Name+"' has been stoped")))
	}
}
func renewLog() {
	ticker := time.NewTicker(24 * time.Hour)
	for {
		<-ticker.C
		renameAllLog()
	}
}
func renameAllLog() {
	reStoreFile(logs, logFile, getFileSize(logFile))
	for _, job := range jobMap {
		reStoreFile(job.RunLog, job.RunLogFile, getFileSize(job.RunLogFile))
		reStoreFile(job.HookLog, job.HookLogFile, getFileSize(job.HookLogFile))
	}
}
func getFileSize(file *os.File) int {
	inf, er := file.Stat()
	if er != nil {
		return 0
	} else {
		return int(inf.Size())
	}
}
func reStoreFile(filepath string, fr *os.File, length int) {
	finfo, er := os.Stat(filepath)
	if er != nil || finfo.Size() < int64(length) {
		return
	}
	fr.Seek(0, 0)
	bb, _ := ioutil.ReadAll(fr)
	var bbs, bbr []byte
	f, ew := os.OpenFile(filepath+"."+time.Now().Format("20060102"), os.O_WRONLY|os.O_APPEND|os.O_CREATE|os.O_SYNC, os.ModePerm)
	if ew != nil {
		return
	}
	lenbb := len(bb)
	if lenbb > length {
		bbs = bb[:length]
		bbr = bb[length:lenbb]
	} else {
		bbs = bb
	}
	if debug {
		fmt.Println("|||||", string(bbs), "||||||", string(bbr), "|||||")
	}
	f.Write(bbs)
	fr.Truncate(0)
	fr.Seek(0, 0)
	fr.Write(bbr)
	fr.Sync()
	f.Close()
}
func startJob(job JobInfo) {
	f, err := os.OpenFile(job.RunLog, os.O_RDWR|os.O_CREATE|os.O_SYNC, os.ModePerm)
	job.HookLogFile, _ = os.OpenFile(job.HookLog, os.O_RDWR|os.O_CREATE|os.O_SYNC, os.ModePerm)
	if err != nil {
		job.pushError(err)
	} else {
		job.RunLogFile = f
	}

	procAttr := &os.ProcAttr{
		Dir: job.RunDir,
		Env: os.Environ(),
		Files: []*os.File{
			os.Stdin,
			job.RunLogFile,
			job.RunLogFile,
		}}
	job.RunLogFile.Write([]byte(fmt.Sprint(time.Now(), " |start Process'"+job.Name+"' \r\n")))
	proc, err := os.StartProcess(job.RunCmd, strings.Split(job.RunCmdExt, " "), procAttr)
	if err != nil {
		job.RunStatus = -1
		job.pushError(err)
		job.RunLogFile.Write([]byte(fmt.Sprint(time.Now(), " |find Errors when starting process:"+err.Error())))
		return
	}
	job.RunLogFile.Write([]byte(fmt.Sprint(time.Now(), " |start Process'"+job.Name+"' successful!\r\n")))
	job.RunStatus = 1
	go waitJobRunEnd(proc, job)
	job.RunProcess = proc
	jobMap[job.Name] = job
}
func startJobHookByName(name string) {
	job, ok := jobMap[name]
	if ok {
		startJobHook(job)
	}
}
func startJobHook(job JobInfo) {
	if job.RunStatus == 1 {
		stopJob(job) //首先停止运行的进程
		job.RunStatus = 6
	}
	f, err := os.OpenFile(job.HookLog, os.O_RDWR|os.O_CREATE|os.O_SYNC, os.ModePerm)
	if err != nil {
		job.pushError(err)
	} else {
		job.HookLogFile = f
	}

	procAttr := &os.ProcAttr{
		Dir: job.HookDir,
		Env: os.Environ(),
		Files: []*os.File{
			os.Stdin,
			job.HookLogFile,
			job.HookLogFile,
		}}
	job.HookLogFile.Write([]byte(fmt.Sprint(time.Now(), " |start Process'"+job.Name+"' \r\n")))
	proc, err := os.StartProcess(job.HookCmd, strings.Split(job.HookCmdExt, " "), procAttr)
	if err != nil {
		job.HookStatus = -1
		job.pushError(err)
		job.HookLogFile.Write([]byte(fmt.Sprint(time.Now(), " |find Errors when starting process:"+err.Error())))
		return
	}
	job.HookLogFile.Write([]byte(fmt.Sprint(time.Now(), " |start Process'"+job.Name+"' successful!\r\n")))
	job.HookStatus = 1
	go waitJobHookEnd(proc, job)
	job.HookProcess = proc
	jobMap[job.Name] = job
}
func waitJobHookEnd(proc *os.Process, job JobInfo) {
	pstate, errs := proc.Wait()
	if errs != nil {
		if debug {
			fmt.Println("wait for job run end error:", errs)
		}
		job.HookStatus = -1
		job.pushError(errs)
		job.HookLogFile.Write([]byte(fmt.Sprintln(time.Now(), " |find Errors when wait for exit:"+errs.Error())))
	} else {
		if pstate.Success() {
			job.HookStatus = 3
		} else {
			if pstate.Exited() {
				job.HookStatus = 4
			} else {
				job.HookStatus = 5
			}
		}
	}
	if debug {
		fmt.Println("wait hook finished,job:", job.Name)
	}
	job.HookLogFile.Write([]byte(fmt.Sprintln(time.Now(), " |process exited")))
	job.HookProcessState = pstate
	job.HookLogFile.Close()
	jobMap[job.Name] = job
	if job.RunStatus == 6 {
		startJob(job)
	}
}
func waitJobRunEnd(proc *os.Process, job JobInfo) {
	pstate, errs := proc.Wait()
	if errs != nil {
		if debug {
			fmt.Println("wait for job run end error:", errs)
		}
		job.RunStatus = -1
		job.pushError(errs)
		job.RunLogFile.Write([]byte(fmt.Sprintln(time.Now(), " |find Errors when wait for exit:"+errs.Error())))
	} else {
		if pstate.Success() {
			job.RunStatus = 3
		} else {
			if pstate.Exited() {
				job.RunStatus = 4
			} else {
				job.RunStatus = 5
			}
		}
	}
	if debug {
		fmt.Println("wait running finished,job:", job.Name, job.RunStatus)
	}
	job.RunLogFile.Write([]byte(fmt.Sprintln(time.Now(), " |process exited")))
	job.RunProcessState = pstate
	if debug {
		fmt.Println(jobMap[job.Name])
		fmt.Println(job)
	}
	jobMap[job.Name] = job

}
func getJobMap(path string) (map[string]JobInfo, error) {
	f, er := os.Open(path)
	var jobs []JobInfo
	maps := make(map[string]JobInfo)
	defer f.Close()
	if er != nil {
		return maps, nil
	}
	bs, ee := ioutil.ReadAll(f)
	if ee != nil {
		return maps, ee
	}
	er = json.Unmarshal(bs, &jobs)
	if er != nil {
		return maps, er
	}
	for _, v := range jobs {
		v.RunStatus = 0
		maps[v.Name] = v
	}
	return maps, nil

}

func getUserMap(userFile string) map[string]UserInfo {
	var users []UserInfo
	userMap := make(map[string]UserInfo)
	bs, _ := readFile(userFile)
	if len(bs) > 0 {
		json.Unmarshal(bs, &users)
	}
	if len(users) > 0 {
		for _, v := range users {
			userMap[v.UserName] = v
		}
	}
	return userMap
}
